package com.enation.app.javashop.manager.security;

import com.enation.app.javashop.framework.cache.Cache;
import com.enation.app.javashop.framework.security.model.*;
import com.enation.app.javashop.framework.util.DateUtil;
import com.enation.app.javashop.framework.util.StringUtil;
import com.enation.app.javashop.framework.util.TokenKeyGenerate;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * jwt token 鉴权管理
 * <p>
 * Created by kingapex on 2018/3/12.
 *
 * @author kingapex
 * @version 1.0
 * @since 7.0.0
 * 2018/3/12
 */
@SuppressWarnings("AlibabaUndefineMagicConstant")
@Component
public class AdminAuthenticationService {

    protected final Log logger = LogFactory.getLog(this.getClass());

    @Value("${spring.cloud.config.profile:dev}")
    private String profile;

    @Autowired
    private Cache cache;

    private static  boolean debug = false;

    /**
     * 鉴权，先获取token，再根据token来鉴权
     * 生产环境要由nonce和时间戳，签名来获取token
     * 开发环境可以直接传token
     *
     * @param req
     */
    public void auth(HttpServletRequest req) {

        String token = this.getToken(req);

        if (logger.isDebugEnabled() && debug) {
            logger.debug(" get token is :[" + token + "]");
        }
        //获取uuid
        String uuid = req.getHeader("uuid");

        if (logger.isDebugEnabled() && debug) {
            logger.debug(" get uuid is :[" + uuid + "]");
        }
        if (StringUtil.notEmpty(token)) {

            Authentication authentication = getAuthentication(token, uuid);
            if (authentication != null) {
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }

        }
    }


    /**
     * 获取token
     * 生产环境要由nonce和时间戳，签名来获取token
     * 开发环境可以直接传token
     *
     * @param req
     * @return
     */
    public String getToken(HttpServletRequest req) {

        String token = null;
        if ("dev".equals(profile) || "test".equals(profile)) {
            token = req.getHeader(JWTConstant.HEADER_STRING);
            if (StringUtil.notEmpty(token)) {
                token = token.replaceAll(JWTConstant.TOKEN_PREFIX, "").trim();
            }
        }
        //生产环境要防重放攻击，token由redis中获取
        if ("prod".equals(profile)) {
            //用户id
            String uid = req.getParameter("uid");
            //时间戳
            String timestamp = req.getParameter("timestamp");
            //随机数
            String nonce = req.getParameter("nonce");
            //签名
            String clientSign = req.getParameter("sign");
            //uuid
            String uuid = req.getHeader("uuid");

            //如果传递参数没有值，则为无权
            if (StringUtil.isEmpty(uid) || StringUtil.isEmpty(timestamp) || StringUtil.isEmpty(nonce) || StringUtil.isEmpty(clientSign)) {
                return null;
            }
            //服务器当前时间。
            long currTime = DateUtil.getDateline();
            //将时间戳转换long类型 用于计算
            long clientTimes = Long.parseLong(timestamp);
            //当前时间大于时间戳60秒即为失效。
            if ((currTime - clientTimes) > JWTConstant.DIFF_TIME) {
                if (logger.isDebugEnabled()  ) {
                    logger.debug("服务器时间：" + currTime);
                    logger.debug("客户端时间：" + clientTimes);
                    logger.debug("验权时间戳超时，判为重放攻击 ");
                }
                return null;
            }
            String key = "{nonce" + uid + "}_" + nonce;
            String value = (String) this.cache.get(key);
            //如果redis中存在说明已经被使用过
            if (value != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("nonce[" + nonce + "] 被使用过,判为重放攻击");
                }
                return null;
            } else {
                //客户端时间戳秒数+失效秒数
                this.cache.put(key, "used", JWTConstant.INVALID_TIME);

            }
            //读取用户的id
            token = StringUtil.toString(cache.get(
                    TokenKeyGenerate.generateAdminAccessToken(uuid, Integer.parseInt(uid))));
            //验证签名 uid+ nonce + timestamp +token
            String serverSign = StringUtil.md5(uid + nonce + timestamp + token);
            if (!clientSign.equals(serverSign)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("key is " + "ACCESS_TOKEN_ADMIN_" + uid + uuid);
                    logger.debug("服务器token: " + token);
                    logger.debug("服务器签名为：" + serverSign);
                    logger.debug("clientSign " + clientSign);
                    logger.debug("签名失败,判为重放攻击 ");
                }
                return null;
            }
        }

        return token;

    }

    /**
     * 根据一个 token 生成授权
     *
     * @param token
     * @return 授权
     */
    private Authentication getAuthentication(String token, String uuid) {
        if (token != null) {
            // parse the token.
            try {
                Claims claims
                        = Jwts.parser()
                        .setSigningKey(JWTConstant.SECRET)
                        .parseClaimsJws(token).getBody();
                Admin admin = buildUser(claims);

                admin.setUuid(uuid);
                //用户名
                String username = claims.get("username", String.class);
                //权限
                String role = claims.get("role", String.class);

                List<GrantedAuthority> auths = new ArrayList<>();

                auths.add(new SimpleGrantedAuthority("ROLE_" + role));

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, auths);

                authentication.setDetails(admin);

                return authentication;
            } catch (Exception e) {
                if (logger.isErrorEnabled()) {
                    logger.error(e);
                }
            }


        }

        return null;
    }


    /**
     * 通过claim生成 系统的用户
     *
     * @param claims jwt的claim
     * @return 系统的用户
     */
    private static Admin buildUser(Claims claims) {
        Integer uid = claims.get("uid", Integer.class);
        String username = claims.get("username", String.class);
        Integer founder = claims.get("founder", Integer.class);
        String role = claims.get("role", String.class);
        Admin admin = new Admin();
        admin.setUid(uid);
        admin.setUsername(username);
        admin.setFounder(founder);
        admin.setRole(role);
        return admin;
    }


}
